/*

    <This Java Class is part of the jMusic API version 1.5, March 2004.>

    Copyright (C) 2000 Andrew Sorensen & Andrew Brown

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or any
    later version.

    This program is distributed in the hope that it will be useful, but
    WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

     */

package tonic.audio;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.UnsupportedAudioFileException;

/**
 * Adapted from JMusic - jm.audio.io.AudioFileIn - to reduce memory consumption and
 * handle multiple channels. 
 *
 * @author Andrew Brown and Andrew Sorensen
 * Modified by Jaymie Strecker
 */
public class AudioReader {
    private AudioInputStream is; 
    private AudioFileFormat fileFormat; 
    private AudioFormat format;

    public AudioReader(AudioInputStream is, File file) throws UnsupportedAudioFileException,
            IOException {
       this.is = is;
       fileFormat = AudioSystem.getAudioFileFormat(file);
       format = is.getFormat();
    }

    /**
     * Returns array of all samples in the audio stream, and closes the stream. 
     *
     * If there are multiple channels, each returned sample is the sum of samples for
     * all channels at that time point. Assumes input samples are arranged like"
     *
     * [<chan 1>,<chan 2>,<chan 1>,<chan 2>,...]
     *
     * Should work for most .wav files found today. May not work for some .wav files
     * encoded with 8-bit samples sizes and not centered about 0.
     *
     */
    public float[] readAudio() throws IOException, UnsupportedAudioFileException {
        int numChannels = format.getChannels();
        int numSamples = fileFormat.getFrameLength() * numChannels;
        float[] sampleData = new float[numSamples / numChannels];

        int sampleSize = format.getSampleSizeInBits() / 8;
        byte[] sampleWord = new byte[sampleSize];
        int samplesPerBuffer = 1024;
        byte[] buffer = new byte[samplesPerBuffer * sampleSize];

        int nextSampleIndex = 0;
        while (nextSampleIndex < sampleData.length) {
            is.read(buffer);

            ByteArrayInputStream bis = new ByteArrayInputStream(buffer);
            for (int i = 0; i < samplesPerBuffer / numChannels && nextSampleIndex < sampleData.length; i++) {
                if (nextSampleIndex == 353157) {
                    System.out.println(); 
                }
                sampleData[nextSampleIndex] = 0;
                for (int j = 0; j < numChannels; j++) {
                    if (bis.read(sampleWord) == -1) {
                        if (j != 0) {
                            throw new IOException("Partial sample found in audio input stream");
                        }
                    } else {
                        sampleData[nextSampleIndex] += getFloat(sampleWord);
                    }
                }
                nextSampleIndex++; 
            }
        }
        is.close();

        return sampleData;
    }

    /**
     * BigEndian conversion
     */
    private float getFloat(byte[] b) {
        boolean bigEndian = format.isBigEndian();
        int sampleSize = format.getSampleSizeInBits() / 8;
        
        float sample = 0.0f;
        int ret = 0;
        int length = b.length;
        for(int i=0;i<b.length;i++,length--) {
            ret |= ((int)(b[i] & 0xFF) << ((((bigEndian) ? length : (i+1)) * 8) - 8));
        }
        switch(sampleSize) {
            case 1:
                if(ret > 0x7F){
                    ret = ~ret + 1;
                    ret &= 0x7F;
                    ret = ~ret + 1;
                }
                sample = (float)((float)ret/(float)Byte.MAX_VALUE);
                break;
            case 2:
                if(ret > 0x7FFF) {
                    ret = ~ret + 1;
                    ret &= 0x7FFF;
                    ret = ~ret + 1;
                }
                sample = (float)((float)ret/(float)Short.MAX_VALUE);
                break;
            case 3:
                if(ret > 0x7FFFFF) {
                    ret = ~ret + 1;
                    ret &= 0x7FFFFF;
                    ret = ~ret + 1;
                }
                sample = (float)((float)ret/8388608f);
                break;
            case 4:
                sample = (float)((double)ret/(double)Integer.MAX_VALUE);
                break;
            default:
                System.err.println("Format not accepted");
        }
        return sample;
    }

}
